import { execSync, exec, ChildProcess } from 'child_process'
import * as path from 'path'
import * as http from 'http'
import { out, Route } from 'f2e-serve'
import { MiddlewareCreater } from 'f2e-server'
import { Server } from 'ws'
import { StreamBuilder } from './streambuilder'
import { DeviceManager, ADBFactory, ApkType, getIdlePorts, logger, usePorts, releasePorts } from './utils'
import { STFEaseConfig, Device, ActionType } from './interfaces'
import { URL } from 'url'
import actions from './actions'
export { STFEaseConfig, Device, ActionType }

const defaultSTFEaseConfig: Required<STFEaseConfig> = {
    adb: 'adb',
    port: 7100,
    port_tcp_start: 7400,
    support_rotate: false,
    screen_width: 360,
    install_pkg: true,
    rate: 30,
    use_javacap: false,
}

export class STFEase {
    config: STFEaseConfig
    device_manager: DeviceManager
    adb_factory: ADBFactory
    constructor (config: STFEaseConfig) {
        this.config = Object.assign<STFEaseConfig, STFEaseConfig, STFEaseConfig>({}, defaultSTFEaseConfig, config)
        
        console.log('config:', config)

        this.device_manager = new DeviceManager()
        this.adb_factory = new ADBFactory(this.config.adb)
        this.adb_factory.clearForward()
    }

    getDevice = async (id: string): Promise<Device> => {
        const _devices = await this.adb_factory.devices()
        const devices = _devices.filter(d => d.status != 'offline').map(d => d.id)
        if (!devices.includes(id)) {
            return null
        }
        let device = this.device_manager.getDevice(id)
        if (!device) {
            device = await this.adb_factory.getDevice(id)
            this.device_manager.saveDevice(device)
            this.install_base(device)
        }
        const { install_pkg } = {...this.config, ...this.device_manager.getDeviceConfig(id)}
        if (install_pkg) {
            await this.adb_factory.installApk(id, ApkType.STFService)
            await this.adb_factory.installApk(id, ApkType.Yosemite)
        }
        return device
    }

    install_base = (device: Device) => {
        let sh_minicap = path.join(__dirname, `../node_modules/@devicefarmer/minicap-prebuilt/prebuilt/${device.abi}/bin/minicap`)
        let so_minicap = path.join(__dirname, `../node_modules/@devicefarmer/minicap-prebuilt/prebuilt/${device.abi}/lib/android-${device.sdk}/minicap.so`)
        let sh_minitouch = path.join(__dirname, `../node_modules/@devicefarmer/minitouch-prebuilt/prebuilt/${device.abi}/bin/minitouch`)
        this.adb_factory.pushFile(device.id, sh_minicap)
        this.adb_factory.pushFile(device.id, so_minicap)
        if (device.sdk > 28) {
            // Android 10 需要安装相关minitouch
            sh_minitouch = path.join(__dirname, `../node_modules/minitouch-prebuilt-support10/prebuilt/${device.abi}/bin/minitouch`)
        }
        this.adb_factory.pushFile(device.id, sh_minitouch)
    }

    connect_device = ({
        device, screen_width, onRelease, onReady,
    }: {
        device: Device,
        screen_width: number,
        onRelease?: () => void,
        onReady?: (port: number, port1: number) => void,
    }) => {
        const { adb_factory } = this
        const config = {...this.config, ...this.device_manager.getDeviceConfig(device.id)}
        const { adb, port_tcp_start } = config
        screen_width = screen_width || config.screen_width
        const size = device.size.override || device.size.physical
        const [_w, _h] = size.match(/\d+/g).map(Number)
        const __h = Math.floor(screen_width * _h / _w)

        /**
         * runtime_size 支持minicap来截取图片
         * 支持旋转的时候，裁剪成方形图，用gm进行裁剪，用以支持不重启的屏幕旋转
         */
        if (config.support_rotate) {
            device.runtime_size = `${_h}x${_h}@${__h}x${__h}`
        } else {
            const size2 = (([w, h]) => `${screen_width}x${Math.floor(screen_width * h / w)}`)(size.match(/\d+/g).map(Number))
            device.runtime_size = `${size}@${size2}`
        }
        adb_factory.clearAdbTask(device.id).then(async function () {
            if (config.use_javacap) {
                device.is_devil = true
            } else {
                const minicap_ok = await adb_factory.start_minicap(device, config)
                device.is_devil = !minicap_ok
            }
            if (device.is_devil) {
                const scale = Math.floor(screen_width * 100 / Number(size.split('x')[0]))
                const javacap_run = await adb_factory.start_javacap(device, scale, onRelease)
                if (!javacap_run) {
                    // javacap 也启动不起来，直接用 adb 处理
                    onReady(0, 0)
                    return
                }
            }
            const touch_running = await adb_factory.start_minitouch(device)
            setTimeout(async () => {
                const [port, port1] = await getIdlePorts(port_tcp_start, 2)
                usePorts(port, port1)
                exec(`${adb} -s ${device.id} forward tcp:${port} localabstract:${device.is_devil ? 'javacap' : 'minicap'}`, function () {
                    if (touch_running) {
                        exec(`${adb} -s ${device.id} forward tcp:${port1} localabstract:minitouch`, function () {
                            onReady && onReady(port, port1)
                        })
                    } else {
                        releasePorts(port1)
                        onReady && onReady(port, 0)
                    }
                })
            }, 0);
        })
    }

    create_route: MiddlewareCreater = (conf) => {
        const route = new Route()
        // 获取所有设备
        route.on('devices', out.JsonpOut(async () => {
            const ids = await this.adb_factory.devices()
            const devices = []
            for (let i = 0; i < ids.length; i++) {
                const _d = ids[i]
                if (_d.status != 'offline') {
                    let d = await this.getDevice(_d.id)
                    d && devices.push(d)
                } else {
                    devices.push(_d)
                }
            }
            return devices
        }))
        // 进入Demo页面
        route.on(/^[\w\.\:\,\-]+$/, () => 'index.html')
        Object.entries(actions(this.adb_factory)).map(([act, fn]) => {
            route.on(`action/${act}`, out.JsonOut(fn as any, conf))
        })
        // 列表页
        route.on('', out.Base('html')(async () => {
            const ids = await this.adb_factory.devices()
            return `<ul>
            ${ids.filter(d => d.status != 'offline').map(d => `<li><a href="/${d.id}">${d.id}</a> </li>`).join('\n')}
        </ul>`
        }))
        return {
            onRoute: route.execute
        }
    }
    create_server = async (server: http.Server) => {
        const t = this
        const { getDevice, connect_device } = this
        /**
         * 根据deviceId索引所有的设备连接组（minicap+minitouch）
         */
        const stream_map = new Map<string, StreamBuilder>()
        const wss = new Server({server})
        wss.on('connection', async function (ws, req) {
            // url模块相关API修改, 使用URL
            const url = new URL('http://localhost' + req.url)
            const device_id = url.pathname.slice(1)
            const params = url.searchParams
            const device = await getDevice(device_id)
            if (!device) {
                ws.send(`${ActionType.ERROR}: no device!`)
                ws.close()
                return
            }

            let builder = stream_map.get(device_id)
            if (!builder) {
                const config = {...t.config, ...t.device_manager.getDeviceConfig(device_id)}
                connect_device({
                    device,
                    screen_width: Number(params.get('screen_width')),
                    onRelease: function () {
                        let builder = stream_map.get(device_id)
                        builder && builder.close()
                    },
                    onReady: function (port, port1) {
                        logger.info(`${device.id} start at minicap:${port}, minitouch:${port1}`)
                        builder = new StreamBuilder({
                            device, port, port1, config,
                            onClose: function () {
                                stream_map.delete(device_id)
                            },
                        })
                        stream_map.set(device_id, builder)
                        builder.addClient(ws)
                    }
                })
            } else {
                builder.addClient(ws)
            }
        })
        wss.on('error', logger.error)
    }
}